package com.effectivsocial.service.api;

import java.io.Serializable;
import java.util.List;
import javax.annotation.PostConstruct;
import jodd.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j;

import org.joda.time.DateTime;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.effectivsocial.domain.PersistentObject;
import com.effectivsocial.domain.User;
import com.effectivsocial.service.security.SecurityUtil;

@Slf4j
@Transactional(readOnly = true)
public abstract class AbstractService<T, ID extends Serializable> implements
		BusinessService<T, ID> {

	private final String DELETED = "deleted";
	private final String CREATED_DATE = "createdDate";
	private final String LAST_MODIFIED_DATE = "lastModifiedDate";
	private final String CREATED_BY = "createdBy";
	private final String LAST_MODIFIED_BY = "lastModifiedBy";
	private final String VERSION = "version";
	private final String ID = "id";

	private CrudRepository<T, ID> repository;

	/**
	 * Subclasses must implement this
	 */
	public List<T> findByDeleted(Pageable pageable, boolean deleted) {
		return null;
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void save(T t) {
		includeAuditForSave(t);
		repository.save(t);
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void save(List<T> entities) {

		for (T t : entities) {

			if (t instanceof PersistentObject ) {

				includeAuditForSave(t);

			}
		}

		repository.save(entities);
	}

	public T findOne(ID id) {
		return repository.findOne(id);
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void delete(T t) {
		BeanUtil.setPropertySilent(t, DELETED, true);
		update(t);
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void delete(List<T> entities) {
		for (T entity : entities) {
			delete(entity);
		}
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void purge(List<T> entities) {
		log.info("Deleting -- {}", entities);
		repository.delete(entities);
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void update(T source) {
		T destination = repository.findOne((ID) BeanUtil
				.getDeclaredPropertySilently(source, ID));

		String ignoreProperties[] = { ID, CREATED_BY, CREATED_DATE,
				LAST_MODIFIED_BY, LAST_MODIFIED_DATE, VERSION };

		BeanUtils.copyProperties(source, destination, ignoreProperties);

		includeAuditForUpdate(destination);
	}

	/**
	 * Some useful methods
	 */
	protected void includeAuditForSave(Object bean) {
		DateTime timestamp = DateTime.now();

		BeanUtil.setPropertySilent(bean, CREATED_DATE, timestamp);
		BeanUtil.setPropertySilent(bean, LAST_MODIFIED_DATE, timestamp);

		User user = SecurityUtil.getUserDetails();

		BeanUtil.setPropertySilent(bean, CREATED_BY, user);
		BeanUtil.setPropertySilent(bean, LAST_MODIFIED_BY, user);
	}
	
	protected User getLoggedInUser() {
		return SecurityUtil.getUserDetails();
	}

	protected void includeAuditForUpdate(Object bean) {
		DateTime timestamp = DateTime.now();
		BeanUtil.setPropertySilent(bean, LAST_MODIFIED_DATE, timestamp);

		User user = SecurityUtil.getUserDetails();
		BeanUtil.setPropertySilent(bean, LAST_MODIFIED_BY, user);
	}

	/**
	 * This is a special method only used for setting 2 properties while
	 * creating an user and company
	 * 
	 * @param bean
	 */
	protected void includeAuditTimestamp(Object bean) {
		DateTime timestamp = DateTime.now();
		BeanUtil.setPropertySilent(bean, CREATED_DATE, timestamp);
		BeanUtil.setPropertySilent(bean, LAST_MODIFIED_DATE, timestamp);
	}

	protected void includeAuditTimestampUpdate(Object bean) {
		DateTime timestamp = DateTime.now();
		BeanUtil.setPropertySilent(bean, LAST_MODIFIED_DATE, timestamp);
	}
	
	protected void includeAuditUser(Object bean, User user) {
		
		
		BeanUtil.setPropertySilent(bean, CREATED_BY, user);
		BeanUtil.setPropertySilent(bean, LAST_MODIFIED_BY, user);
	}

	/**
	 * @return the repository
	 */
	public CrudRepository<T, ID> getRepository() {
		return repository;
	}

	/**
	 * @param repository
	 *            the repository to set
	 */
	public void setRepository(CrudRepository<T, ID> repository) {
		this.repository = repository;
	}

	@PostConstruct
	public void init() {
		initInternal();
	}

	protected abstract void initInternal();
}
